package ca.concordia.soen6461.wichf.ui.web;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import ca.concordia.soen6461.wichf.controller.Controller;
import ca.concordia.soen6461.wichf.controller.Event;
import ca.concordia.soen6461.wichf.controller.Session;
import ca.concordia.soen6461.wichf.controller.View;
import ca.concordia.soen6461.wichf.controller.dsl.NavigationModel;

public abstract class BaseServlet extends HttpServlet {

	protected class ViewMapping {
		
		private View<?> view;
		private String path;
		
		public ViewMapping(View<?> view, String path) {
			this.view = view;
			this.path = path;
		}

		public View<?> getView() {
			return view;
		}

		public String getPath() {
			return path;
		}
		
	}
	
	private static final long serialVersionUID = -1727744853065385698L;
	
	private Map<String, BaseEventParser<?>> eventParsers =  new HashMap<String, BaseEventParser<?>>();
	private Map<View<?>, String> viewMappings = new HashMap<View<?>, String>();
	private NavigationModel navigationModel;
	private Controller controller;
	
	private void registerParsers(BaseEventParser<?>... parsers) {
		for (BaseEventParser<?> parser : parsers) {
			eventParsers.put(parser.getEventType().getSimpleName(), parser);
		}
	}
	
	private void registerMappings(ViewMapping... mappings) {
		for (ViewMapping mapping : mappings) {
			viewMappings.put(mapping.getView(), mapping.getPath());
		}
	}
	
	protected final ViewMapping mapping(View<?> view, String path) {
		return new ViewMapping(view, path);
	}
	
	abstract protected BaseEventParser<?>[] createParsers();
	
	abstract protected ViewMapping[] createViewMappings();
	
	abstract protected NavigationModel createNavigationModel();
	
	public BaseServlet() {
	}
	
	@Override
	public void init() throws ServletException {
		super.init();
		registerParsers(createParsers());
		registerMappings(createViewMappings());
		navigationModel = createNavigationModel();
		controller = new Controller(navigationModel);
	}
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doHandle(req, resp);
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doHandle(req, resp);
	}
	
	private void doHandle(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		Session session = getSession(req);
		String eventType = getEvent(req);
		BaseEventParser<?> eventParser = eventParsers.get(eventType);
		Event event = eventParser != null ? eventParser.parse(req, session) : new Event(session);
		View<?> view = null;
		try {
			view = controller.handle(event);
		} catch (Exception e) {
			event.getMessages().add("Failed to handle request: " + e.getMessage());
			view = event.getSession().getViewStack().currentView();
		}
		req.setAttribute("view", view.render(event));
		req.setAttribute("event", event);
		String path = viewMappings.get(view);
		getServletContext().getRequestDispatcher(path).forward(req, resp);
	}

	private String getEvent(HttpServletRequest req) {
		String eventType = req.getParameter("event");
		for (String parameter : req.getParameterMap().keySet()) {
			if (parameter.endsWith("Event")) {
				eventType = parameter;
				break;
			}
		}
		return eventType;
	}

	protected Session getSession(HttpServletRequest req) {
		Session session = (Session) req.getSession().getAttribute("session");
		if (session == null) {
			session = new Session();
			req.getSession().setAttribute("session", session);
		}
		return session;
	}

}
